﻿// <copyright file="UPnPService.cs" company="EffuEfface">
//      Copyright (c) Effu Efface Consulting  All rights reserved.
// </copyright>
// <author>Ryan R. Sites</author>

namespace TVersiPlay.Services
{
    using System;
    using System.Collections.Generic;
    using System.Xml;
    using Microsoft.Practices.Prism.Events;
    using OpenSource.UPnP;
    using TVersiPlay.Common;
    using TVersiPlay.Common.Enumerations;
    using TVersiPlay.Common.Interfaces;
    using TVersiPlay.DTO;
    using TVersiPlay.Events;

    /// <summary>
    /// 
    /// </summary>
    public class UPnPControlPointService : IUPnPControlPointService
    {
        #region declarations

        private IAppSettingsService appSettings;
        private IEventAggregator eventAggregator;
        private INavigationService navigate;

        private UPnPDeviceDiscovery mediaServerDiscovery;
        private ContentDirectory contentDirectory;
        private List<UPnPServer> availableTVersityServers;
        private UPnPDevice connectedTVersityServer;

        private TVersiPlayMediaPlayer mediaPlayer;

        private string audioContainerId;
        private string videoContainerId;
        private string photoContainerId;
        private string internetContainerId;
        private string myServerId;     
        

        #endregion //declarations

        #region constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="UPnPControlPointService"/> class.
        /// </summary>
        /// <param name="appSettings">The app settings.</param>
        /// <param name="eventAggregator">The event aggregator.</param>
        public UPnPControlPointService(IAppSettingsService appSettings, IEventAggregator eventAggregator, INavigationService navigate)
        {
            this.appSettings = appSettings;
            this.eventAggregator = eventAggregator;
            this.navigate = navigate;

            mediaServerDiscovery = new UPnPDeviceDiscovery();
            availableTVersityServers = new List<UPnPServer>();
            mediaServerDiscovery.OnAddedDevice += new UPnPDeviceDiscovery.DiscoveryHandler(OnServerAdded);
            mediaServerDiscovery.OnRemovedDevice += new UPnPDeviceDiscovery.DiscoveryHandler(OnServerRemoved);

            this.mediaPlayer = new TVersiPlayMediaPlayer();
            this.mediaPlayer.Start();
           
        }
        
        #endregion //constructors

        #region properties
      
        #endregion //properties

        #region methods

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public void Start()
        {
            myServerId = this.appSettings.GetAppSettings().UPnPServerId;

            mediaServerDiscovery.Start();
        }

        /// <summary>
        /// Ts the versity server available.
        /// </summary>
        /// <returns></returns>
        public bool TVersityServerAvailable()
        {
            return this.connectedTVersityServer != null;
        }

        /// <summary>
        /// Gets the available servers.
        /// </summary>
        /// <returns></returns>
        public List<UPnPServer> GetAvailableServers()
        {
            if (this.availableTVersityServers == null)
                this.availableTVersityServers = new List<UPnPServer>();

            return this.availableTVersityServers;
        }

        /// <summary>
        /// Called when [server added].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="d">The d.</param>
        private void OnServerAdded(UPnPDeviceDiscovery sender, UPnPDevice d)
        {
            if (d.ModelDescription != null)// && (d.ModelDescription.ToLower().Contains("tversity") || d.ModelName.ToLower().Contains("tversity")))
            {
                if (d.DeviceURN.ToString().Contains("urn:schemas-upnp-org:device:MediaRenderer:1"))
                {
                    Console.WriteLine("Test");
                }
                else if (d.DeviceURN.ToString().Contains("urn:schemas-upnp-org:device:MediaServer:1"))
                {
                    UPnPServer server = new UPnPServer();
                    server.IsActive = false;

                    if (d.UniqueDeviceName == this.myServerId)
                    {
                        server.IsActive = true;
                        this.SetCurrentTVersityServer(d);
                        this.connectedTVersityServer = d;
                    }

                    server.Server = d;
                    server.FriendlyName = d.FriendlyName;
                    this.availableTVersityServers.Add(server);

                    RefreshListOfDevices();
                }
                
            }

        }

        /// <summary>
        /// Called when [server removed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="d">The d.</param>
        private void OnServerRemoved(UPnPDeviceDiscovery sender, UPnPDevice d)
        {
            try
            {
                foreach (var e in this.availableTVersityServers)
                {
                    if (e.Server.Equals(d))
                    {
                        if (d.UniqueDeviceName == this.myServerId)
                            this.connectedTVersityServer = null;

                        this.availableTVersityServers.Remove(e);
                        break;
                    }
                }
               
                this.RefreshListOfDevices();
            }
            catch
            {

            }
        }

        /// <summary>
        /// Refreshes the list of devices.
        /// </summary>
        private void RefreshListOfDevices()
        {
            this.eventAggregator.GetEvent<UPnPServerRefreshedEvent>().Publish(null);
        }

        /// <summary>
        /// Sets the current T versity server.
        /// </summary>
        /// <param name="deviceToSet">The device to set.</param>
        public void SetCurrentTVersityServer(UPnPDevice deviceToSet)
        {
            UPnPService[] contentDirectoryServices;
            UPnPService cdService;

            //retrieve Content Directory service from the UPnP Media Server
            contentDirectoryServices = deviceToSet.GetServices("urn:schemas-upnp-org:service:ContentDirectory:1");

            for (int i = 0; i < this.availableTVersityServers.Count; i++)
            {
                this.availableTVersityServers[i].IsActive = false;

            }

            var result =  this.availableTVersityServers.Find(x => x.Server == deviceToSet);

            if (result != null)
            {
                result.IsActive = true;
                this.connectedTVersityServer = result.Server;
                this.myServerId = result.Server.UniqueDeviceName;
              
                AppSettings dto = new AppSettings() { UPnPServerId = result.Server.UniqueDeviceName };
                this.appSettings.UpdateAndSaveAppSettings(dto);
            }

            if (contentDirectoryServices != null)
            {
                cdService = contentDirectoryServices[0];
                contentDirectory = new ContentDirectory(cdService);
                if (contentDirectory != null)
                {
                    Action<DTO.BrowseResults> callBack = results =>
                        {
                            this.RefreshListOfDevices();
                        };


                    this.BrowseMediaServer(BrowseType.Root, ContentDirectory.Enum_A_ARG_TYPE_BrowseFlag.BROWSEDIRECTCHILDREN, callBack);
                    
                }
            }
        }


        /// <summary>
        /// Browses the media server.
        /// </summary>
        /// <param name="typeOfBrowse">The type of browse.</param>
        /// <param name="browseFlag">The browse flag.</param>
        public void BrowseMediaServer(BrowseType typeOfBrowse, ContentDirectory.Enum_A_ARG_TYPE_BrowseFlag browseFlag, Action<DTO.BrowseResults> callBack)
        {
            string objectIdToBrowse = "0";

            switch (typeOfBrowse)
            {
                case BrowseType.Audio:
                    objectIdToBrowse = this.audioContainerId;
                    break;
                case BrowseType.Video:
                    objectIdToBrowse = this.videoContainerId;
                    break;
                case BrowseType.Photo:
                    objectIdToBrowse = this.photoContainerId;
                    break;
                case BrowseType.Internet:
                    objectIdToBrowse = this.internetContainerId;
                    break;
                case BrowseType.Root:
                    break;
            }

            this.BrowseMediaServer(typeOfBrowse, objectIdToBrowse, browseFlag, callBack);
        }

        public void BrowseMediaServer(BrowseType typeOfBrowse, string containerId, ContentDirectory.Enum_A_ARG_TYPE_BrowseFlag browseFlag, Action<DTO.BrowseResults> callBack)
        {
            ContentDirectory.Delegate_OnResult_Browse contentDirectoryCallback = (
                ContentDirectory sender,
                System.String ObjectID, 
                TVersiPlay.Common.ContentDirectory.Enum_A_ARG_TYPE_BrowseFlag BrowseFlag, 
                System.String Filter, 
                System.UInt32 StartingIndex, 
                System.UInt32 RequestedCount, 
                System.String SortCriteria, 
                System.String Result, 
                System.UInt32 NumberReturned, 
                System.UInt32 TotalMatches, 
                System.UInt32 UpdateID, 
                UPnPInvokeException e, 
                object _Tag) =>
                {
                    var results = this.ParseResults(typeOfBrowse,ObjectID, BrowseFlag, Filter, StartingIndex, RequestedCount, SortCriteria, Result, NumberReturned, TotalMatches, UpdateID, e, _Tag);
                    
                    callBack(results);
                };

            contentDirectory.Browse(containerId, browseFlag, "*", 0, 0, string.Empty, null, contentDirectoryCallback);  
        }



        /// <summary>
        /// CD_s the on result_ search.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="ContainerID">The container ID.</param>
        /// <param name="SearchCriteria">The search criteria.</param>
        /// <param name="Filter">The filter.</param>
        /// <param name="StartingIndex">Index of the starting.</param>
        /// <param name="RequestedCount">The requested count.</param>
        /// <param name="SortCriteria">The sort criteria.</param>
        /// <param name="Result">The result.</param>
        /// <param name="NumberReturned">The number returned.</param>
        /// <param name="TotalMatches">The total matches.</param>
        /// <param name="UpdateID">The update ID.</param>
        /// <param name="e">The e.</param>
        /// <param name="_Tag">The _ tag.</param>
        private void cd_OnResult_Search(ContentDirectory sender, string ContainerID, string SearchCriteria, string Filter, uint StartingIndex, uint RequestedCount, string SortCriteria, string Result, uint NumberReturned, uint TotalMatches, uint UpdateID, UPnPInvokeException e, object _Tag)
        {
            throw new NotImplementedException();
        }

        private DTO.BrowseResults ParseResults(BrowseType typeOfBrowse, string ObjectID, ContentDirectory.Enum_A_ARG_TYPE_BrowseFlag BrowseFlag, string Filter, uint StartingIndex, uint RequestedCount, string SortCriteria, string Result, uint NumberReturned, uint TotalMatches, uint UpdateID, UPnPInvokeException e, object _Tag)
        {
            XmlDocument xmlDoc = new XmlDocument();
            XmlNodeList containerNodes;
            XmlNodeList mediaNodes;
            XmlNodeList containerDetailNodes;

            string containerId;
            string containerParentId;
            string friendlyContainerName = string.Empty;
            string containerIcon = string.Empty;
            string containerType = string.Empty;


            DTO.BrowseResults browseResults = new DTO.BrowseResults();

            browseResults.TypeOfBrowse = typeOfBrowse;

            // Determine where the Browse originated from
            if (ObjectID == "0")
                browseResults.OriginOfBrowse = BrowseOrigin.RootSearch;
            else if (ObjectID == this.audioContainerId || ObjectID == this.videoContainerId || ObjectID == this.photoContainerId || ObjectID == this.internetContainerId)
                browseResults.OriginOfBrowse = BrowseOrigin.MainMenu;
            else
                browseResults.OriginOfBrowse = BrowseOrigin.MediaBrowse;

            try
            {
                xmlDoc.LoadXml(Result.Replace("&Amp", "&amp"));
            }
            catch (Exception ex)
            {
                //ToDo: Handle mal-formed xml browse results
            }

            containerNodes = xmlDoc.GetElementsByTagName("container");
            mediaNodes = xmlDoc.GetElementsByTagName("item");

            if (containerNodes.Count > 0)
            {
                foreach (XmlNode containerNode in containerNodes)
                {
                    DTO.UPnPContainer containerToAdd = new DTO.UPnPContainer();

                    containerId = containerNode.Attributes["id"].Value;
                    containerParentId = containerNode.Attributes["parentID"].Value; ;
                    containerDetailNodes = containerNode.ChildNodes;

                    foreach (XmlNode containerDetailNode in containerDetailNodes)
                    {
                        switch (containerDetailNode.LocalName.ToLower())
                        {
                            case "title":
                                friendlyContainerName = containerDetailNode.InnerText;
                                break;
                            case "icon":
                                containerIcon = containerDetailNode.InnerText;
                                break;
                            case "class":
                                containerType = containerDetailNode.InnerText;
                                break;
                        }
                    }

                    containerToAdd.ContainerId = containerId;
                    containerToAdd.ContainerFriendlyName = friendlyContainerName;
                    containerToAdd.ContainerParentId = containerParentId;
                    containerToAdd.ContainerIcon = containerIcon;
                    containerToAdd.ContainerType = containerType;

                    if (browseResults.ContainerResults.Count == 0)
                    {
                        string backContainerId = string.Empty;
                        if (containerParentId.LastIndexOf("/") > 0)
                            backContainerId = containerParentId.Substring(0, containerParentId.LastIndexOf("/"));
                        else
                            backContainerId = containerParentId;
                        UPnPContainer backContainer = new UPnPContainer() { ContainerType = browseResults.OriginOfBrowse == BrowseOrigin.MainMenu ? "Main Menu" : string.Empty, ContainerFriendlyName = "", ContainerIcon = "/TVersiPlay.Services;component/BackArrow.png", ContainerId = backContainerId };
                        browseResults.ContainerResults.Add(backContainer);
                    }

                    //If root search - have to map root container ids to main menu
                    if (ObjectID == "0")
                    {
                        if (containerToAdd.ContainerFriendlyName.ToLower().Contains("audio") || containerToAdd.ContainerFriendlyName.ToLower().Contains("music"))
                            this.audioContainerId = containerToAdd.ContainerId;
                        else if (containerToAdd.ContainerFriendlyName.ToLower().Contains("video"))
                            this.videoContainerId = containerToAdd.ContainerId;
                        else if (containerToAdd.ContainerFriendlyName.ToLower().Contains("photo"))
                            this.photoContainerId = containerToAdd.ContainerId;
                        else if (containerToAdd.ContainerFriendlyName.ToLower().Contains("internet"))
                            this.internetContainerId = containerToAdd.ContainerId;
                    }
                    else
                        browseResults.ContainerResults.Add(containerToAdd);
                }
            }

            if (mediaNodes.Count > 0)
            {
                XmlNodeList mediaDetailNodes;
                string mediaId;

                foreach (XmlNode mediaNode in mediaNodes)
                {

                    string MediaId = string.Empty;
                    string MediaName = string.Empty;
                    string Creator = string.Empty;
                    string Date = string.Empty;
                    string Album = string.Empty;
                    string AlbumArtUri = string.Empty;
                    string Artist = string.Empty;
                    string Genre = string.Empty;
                    string Icon = string.Empty;
                    string MediaUrl = string.Empty;
                    string Bitrate = string.Empty;
                    string Duration = string.Empty;
                    string Resolution = string.Empty;
                    string MediaType = string.Empty;

                    mediaId = mediaNode.Attributes["id"].Value;

                    mediaDetailNodes = mediaNode.ChildNodes;

                    foreach (XmlNode mediaDetailNode in mediaDetailNodes)
                    {

                        switch (mediaDetailNode.LocalName.ToLower())
                        {
                            case "creator":
                                Creator = mediaDetailNode.InnerText;
                                break;
                            case "date":
                                Date = mediaDetailNode.InnerText;
                                break;
                            case "title":
                                MediaName = mediaDetailNode.InnerText;
                                break;
                            case "artist":
                                Artist = mediaDetailNode.InnerText;
                                break;
                            case "album":
                                Album = mediaDetailNode.InnerText;
                                break;
                            case "icon":
                                Icon = mediaDetailNode.InnerText;
                                break;
                            case "albumarturi":
                                AlbumArtUri = mediaDetailNode.InnerText;
                                break;
                            case "class":
                                MediaType = mediaDetailNode.InnerText;
                                break;
                            case "genre":
                                Genre = mediaDetailNode.InnerText;
                                break;
                            case "res":
                                {
                                    for (int i = 0; i < mediaDetailNode.Attributes.Count; i++)
                                    {
                                        switch (mediaDetailNode.Attributes[i].Name.ToString().ToLower())
                                        {
                                            case "protocolinfo":
                                                {
                                                    string extension;
                                                    if (mediaDetailNode.Attributes[i].Value.ToString().ToLower().Contains("mp3"))
                                                        MediaUrl = mediaDetailNode.InnerText + ".mp3";
                                                    else if (mediaDetailNode.Attributes[i].Value.ToString().ToLower().Contains("wma"))
                                                        MediaUrl = mediaDetailNode.InnerText + ".wma";
                                                    else if (mediaDetailNode.Attributes[i].Value.ToString().ToLower().Contains("jpeg"))
                                                        extension = ".jpg";
                                                    else if (mediaDetailNode.Attributes[i].Value.ToString().ToLower().Contains("video/mpeg"))
                                                        extension = ".avi";
                                                    else
                                                        extension = ".mp3";

                                                }
                                                break;
                                            case "bitrate":
                                                Bitrate = mediaDetailNode.Attributes[i].Value.ToString();
                                                break;
                                            case "duration":
                                                Duration = mediaDetailNode.Attributes[i].Value.ToString();
                                                break;
                                            case "resolution":
                                                Resolution = mediaDetailNode.Attributes[i].Value.ToString();
                                                break;
                                        }
                                    }

                                }
                                break;
                        }
                    }

                    browseResults.MediaResults.Add(new DTO.UPnPMedia()
                    {
                        MediaId = mediaId,
                        MediaName = MediaName,
                        Album = Album,
                        AlbumArtUri = AlbumArtUri,
                        Artist = Artist,
                        Creator = Creator,
                        Date = Date,
                        Icon = Icon,
                        Genre = Genre,
                        MediaUrl = MediaUrl,
                        Bitrate = Bitrate,
                        Duration = Duration,
                        Resolution = Resolution,
                        MediaType = MediaType
                    });
                }
            }

            return browseResults;
        }

        #endregion //methods

        #region events
        #endregion //events
       
    }
}
